{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### pytest中的case前后置处理与数据驱动\n",
    "在自动化测试中，前后置处理(setup和teardown)是非常重要的部分，它帮助我们在测试执行前准备环境，并在测试完成后进行清理\n",
    "\n",
    "#### 1.UnitTest中的前后置处理\n",
    "在经典的UnitTest框架中，前后置处理通过setup和teardown实现，通过定义setup_class或teardown_class，我们可以确保某些操作在所有测试用例执行前后只执行一次。\n",
    "```python\n",
    "def setup_method(self):\n",
    "    print(\"前置处理，每个测试方法执行前都会运行\")\n",
    "\n",
    "def teardown_method(self):\n",
    "    print(\"后置处理，每个测试方法执行后都会运行\")\n",
    "```\n",
    "setup_method：在每个测试用例执行之前运行\n",
    "teardown_method：在每个测试用例执行之后运行\n",
    "\n",
    "#### 2.pytest中的前后置处理\n",
    "pytest提供了一种更灵活的方式来实现前后置处理，即通过pytest.fixture装饰器来实现，yield关键字在Pytest中作为前后置处理的分隔点，yield之前的代码在测试前执行，yield之后的代码在测试后执行\n",
    "```python\n",
    "import pytest\n",
    "\n",
    "@pytest.fixture\n",
    "def setup_teardown():\n",
    "    print(\"前置处理\")\n",
    "    yield\n",
    "    print(\"后置处理\")\n",
    "```\n",
    "在每个测试用例执行时，setup_teardown中的前置处理会自动运行，在测试完成后，后置处理自动触发\n",
    "\n",
    "#### 3.conftest.py文件中的前后置处理\n",
    "pytest提供了第三种前后置处理方式：在conftest.py文件中定义全局的前后置处理逻辑，conftest.py是一个特殊文件，可以被所有测试用例共享，适合用于初始化全局状态\n",
    "```python\n",
    "# conftest.py\n",
    "import pytest\n",
    "\n",
    "@pytest.fixture(scope = \"session\", autouse = True)\n",
    "def global_setup():\n",
    "    print(\"全局前置处理\")\n",
    "    yield\n",
    "    print(\"全局后置处理\")\n",
    "```\n",
    "scope参数可以定义前后置操作的作用范围：function、class、module、package、session\n",
    "autouse = Ture表示该fixture会自动应用于所有测试用例\n",
    "\n",
    "#### 4.pytest作用域管理\n",
    "在pytest.fixture中，通过scope参数可以灵活管理前后置处理的执行频率：\n",
    "- functin: 每个测试函数执行前后运行一次\n",
    "- class: 在测试类的作用域内，前后置处理仅执行一次\n",
    "- module: 跨文件的情况下，每个模块只执行一次前后置处理\n",
    "- session: 测试会话期间，全局的前后置处理仅执行一次\n",
    "```python\n",
    "@pytest.fixture(scope = \"class\")\n",
    "def setup_teardown():\n",
    "    print(\"类的前置处理\")\n",
    "    yield\n",
    "    print(\"类的后置处理\")\n",
    "```\n",
    "这种灵活性让我们可以根据具体需求，控制前后置处理的作用范围，从而提高测试的效率与可维护性\n",
    "\n",
    "#### 5.示例解析\n",
    "以下是一个简单的例子，通过pytest.fixture实现前后置处理：\n",
    "```python\n",
    "import pytest\n",
    "\n",
    "@pytest.fixture\n",
    "def setup_teardown():\n",
    "    print(\"执行前\")\n",
    "    yield\n",
    "    print(\"执行后\")\n",
    "\n",
    "def test_case_1(setup_teardown):\n",
    "    print(\"测试用例1执行\")\n",
    "\n",
    "def test_case_2(setup_teardown):\n",
    "    print(\"测试用例2执行\")\n",
    "```\n",
    "在每个测试用例执行前，pytest会自动执行setup_teardown中的前置处理部分，在测试用例执行后，后置处理部分会自动触发。\n",
    "\n",
    "#### 6.参数化处理\n",
    "pytest支持通过pytest.mark.parametrize进行参数化测试，可以一次性测试多个输入组合，减少重复编写代码的情况。\n",
    "```python\n",
    "import pytest\n",
    "\n",
    "@pytest.mark.parametrize(\"input, expected\", [\n",
    "    (1, 2),\n",
    "    (2, 4),\n",
    "    (3, 6)\n",
    "])\n",
    "def test_multiply(input, expected):\n",
    "    assert input * 2 == expected\n",
    "```\n",
    "parametrize提供了一种简洁的方式定义测试输入和期望输出，通过一次性传递多个值，减少冗余代码。\n",
    "\n",
    "#### 7.结合conftest.py的多文件支持\n",
    "conftest.py文件可以根据项目结构放置在不同层级的目录中，从而控制前后置处理的作用范围，例如：\n",
    "- 在根目录下的conftest.py可能用于全局的前后置处理\n",
    "- 在子模块中的conftest.py可以针对该模块内的测试用例进行特定处理\n",
    "```python\n",
    "# 项目根目录conftest.py\n",
    "@pytest.fixture(scope = \"session\")\n",
    "def global_setup():\n",
    "    print(\"全局前置处理\")\n",
    "\n",
    "# 子模块 test_case 目录下的conftest.py\n",
    "@pytest.fixture(scope = \"function\")\n",
    "def module_setup():\n",
    "    print(\"模块前置处理\")\n",
    "```\n",
    "\n",
    "#### 8.pytest与YAML文件结合实现数据驱动\n",
    "在接口自动化测试中，将测试数与测试逻辑分离是一种良好的实践，pytest结合YAML文件可以很好地实现这一点，使得我们的测试更加灵活和易于维护。\n",
    "\n",
    "##### 8.1准备工作\n",
    "首先需要安装必要的库：\n",
    "```bash\n",
    "pip install pytest pyyaml\n",
    "```\n",
    "然后创建一个用于读取YAML文件的工具类："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# utils/YamlUtil.py\n",
    "import yaml\n",
    "\n",
    "class YamlReader:\n",
    "    def __init__(self, yaml_file):\n",
    "        self.yaml_file = yaml_file\n",
    "\n",
    "    def data(self):\n",
    "        with open(self.yaml_file, 'r', encoding='utf-8') as f:\n",
    "            return yaml.self_load(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### 8.2编写测试用例\n",
    "假设我们有一个登录接口需要测试，可以这样编写测试用例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# test_login.py\n",
    "from utils.YamlUtil import YamlReader\n",
    "from utils.RequestsUtil import RequestsUtil\n",
    "\n",
    "def test_login():\n",
    "    # 读取YAML文件\n",
    "    case_info = YamlReader('../testcases/login/login.yaml').data()\n",
    "\n",
    "    # 提取接口信息\n",
    "    url = str(case_info[0]['request']['url'])\n",
    "    method = case_info[0]['request']['method']\n",
    "    headers = case_info[0]['request']['headers']\n",
    "    data = case_info[0]['request']['data']\n",
    "\n",
    "    # 发送请求\n",
    "    req = RequestsUtil()\n",
    "    res = req.send_request(url = url, data = data, headers = headers, method = method)\n",
    "\n",
    "    # 断言\n",
    "    assert res.json()['message'] == '登录成功'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### 8.3YAML文件结构\n",
    "对应的YAML文件(login.yaml)可能如下所示：\n",
    "```yaml\n",
    "-\n",
    "  name: 登录测试\n",
    "  request:\n",
    "    url: /api/login\n",
    "    method: POST\n",
    "    headers:\n",
    "      Content-Type: application/json\n",
    "    data:\n",
    "      username: testuser\n",
    "      password: password134\n",
    "    validate:\n",
    "      - eq: [status_code, 200]\n",
    "      - eq: [$.message, \"登录成功\"]\n",
    "```\n",
    "\n",
    "##### 8.4优化测试框架\n",
    "为了进一步简化测试用例的编写，我们可以在APIUtil.py文件中定义一个通用方法："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# utils/APIUtil.py\n",
    "from utils.YamlUtil import YamlReader\n",
    "from utils.RequestsUtil import RequestsUtil\n",
    "\n",
    "class APIUtil:\n",
    "    @staticmethod\n",
    "    def get_case_info(yaml_file):\n",
    "        case_info = YamlReader(yaml_file).data()\n",
    "        url = str(case_info[0]['request']['url'])\n",
    "        method = case_info[0]['request']['method']\n",
    "        headers = case_info[0]['request']['headers']\n",
    "        data = case_info[0]['request']['data']\n",
    "        return url, method, headers, data\n",
    "    \n",
    "    @staticmethod\n",
    "    def send_request(yaml_file):\n",
    "        url, method, headers, data = APIUtil.get_case_info(yaml_file)\n",
    "        req = RequestsUtil()\n",
    "        return req.send_request(url=url, data=data, headers=headers, method=method)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在我们的测试用例可以更加简洁："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# test_login.py\n",
    "from utils.APIUtil import APIUtil\n",
    "\n",
    "def test_login():\n",
    "    res = APIUtil.send_request('../testcases/login/login.yaml')\n",
    "    assert res.json()['message'] == '登录成功'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### 8.5数据驱动测试\n",
    "利用pytest的参数化功能，我们可以轻松实现数据驱动测试："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pytest\n",
    "from utils.APIUtil import APIUtil\n",
    "\n",
    "@pytest.mark.parametrize(\"yaml_file\", [\n",
    "    '../testcases/login/login_sucess.yaml',\n",
    "    '../testcases/login/login_fail_wrong_password.yaml',\n",
    "    '../testcases/login/login_fail_invalid_user.yaml'\n",
    "])\n",
    "def test_login(yaml_file):\n",
    "    res = APIUtil.send_request(yaml_file)\n",
    "    assert res.status_code == 200"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "deeppacket",
   "language": "python",
   "name": "deeppacket"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
